﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CSharp;
using NMG.Core.Domain;
using NMG.Core.TextFormatter;
using NMG.Core.Util;

namespace NMG.Core.Generator
{
    public class AttributeGenerator : AbstractGenerator
    {
        private readonly ApplicationPreferences applicationPreferences;

        public AttributeGenerator(ApplicationPreferences applicationPreferences, Table table)
            : base(applicationPreferences.FolderPath, "Mapping", applicationPreferences.TableName, applicationPreferences.NameSpace, applicationPreferences.AssemblyName, applicationPreferences.Sequence, table, applicationPreferences)
        {
            this.applicationPreferences = applicationPreferences;
        }

        public override void Generate()
        {
            var compileUnit = GetCompileUnit();
            WriteToFile(compileUnit, Formatter.FormatSingular(tableName));
        }

        public CodeCompileUnit GetCompileUnit()
        {
            var codeGenerationHelper = new CodeGenerationHelper();
            // This is where we construct the constructor
            var compileUnit = codeGenerationHelper.GetCodeCompileUnit(nameSpace, Table.Name.MakeSingular(), false, true);

            var mapper = new DataTypeMapper();
            var newType = compileUnit.Namespaces[0].Types[0];
            newType.IsPartial = applicationPreferences.GeneratePartialClasses;
            foreach (var pk in Table.PrimaryKey.Columns)
            {
                var mapFromDbType = mapper.MapFromDBType(this.applicationPreferences.ServerType, pk.DataType, pk.DataLength, pk.DataPrecision, pk.DataScale);
                var declarationId = new CodeAttributeDeclaration("Id");
                declarationId.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(0)));
                declarationId.Arguments.Add(new CodeAttributeArgument("Name", new CodePrimitiveExpression(pk.Name)));
                declarationId.Arguments.Add(new CodeAttributeArgument("Column", new CodePrimitiveExpression(pk.Name)));
                declarationId.Arguments.Add(new CodeAttributeArgument("Length", new CodePrimitiveExpression(pk.DataLength)));
                var declarationKey = new CodeAttributeDeclaration("Key");
                declarationKey.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(1)));
                newType.Members.Add(codeGenerationHelper.CreateAutoProperty(mapFromDbType.ToString(), pk.Name, pk.Comment, declarationId, declarationKey));
            }

            foreach (var fk in Table.ForeignKeys)
            {
                var declarationManyToOne = new CodeAttributeDeclaration("ManyToOne");
                declarationManyToOne.Arguments.Add(new CodeAttributeArgument("ClassType", new CodePrimitiveExpression("typeof(" + fk.References + ")")));
                declarationManyToOne.Arguments.Add(new CodeAttributeArgument("Column", new CodePrimitiveExpression(fk.Name)));
                declarationManyToOne.Arguments.Add(new CodeAttributeArgument("OuterJoin", new CodePrimitiveExpression("OuterJoinStrategy.True")));
                newType.Members.Add(codeGenerationHelper.CreateAutoProperty(fk.References.MakeSingular(), fk.References.MakeSingular(), "", declarationManyToOne));
            }

            foreach (var property in Table.Columns.Where(x => x.IsPrimaryKey != true && x.IsForeignKey != true))
            {
                var declaration = new CodeAttributeDeclaration("Property");
                declaration.Arguments.Add(new CodeAttributeArgument("Column", new CodePrimitiveExpression(property.Name)));

                if (property.DataLength.HasValue)
                    declaration.Arguments.Add(new CodeAttributeArgument("Length", new CodePrimitiveExpression(property.DataLength)));

                if (!property.IsNullable)
                {
                    declaration.Arguments.Add(new CodeAttributeArgument("NotNull", new CodePrimitiveExpression(true)));
                }

                var mapFromDbType = mapper.MapFromDBType(this.applicationPreferences.ServerType, property.DataType, property.DataLength, property.DataPrecision, property.DataScale);
                newType.Members.Add(codeGenerationHelper.CreateAutoProperty(mapFromDbType.ToString(), property.Name, property.Comment, declaration));
            }

            foreach (var children in Table.HasManyRelationships)
            {
                var declarationBag = new CodeAttributeDeclaration("Bag");
                declarationBag.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(0)));
                declarationBag.Arguments.Add(new CodeAttributeArgument("Lazy", new CodePrimitiveExpression("CollectionLazy.True")));
                declarationBag.Arguments.Add(new CodeAttributeArgument("Cascade", new CodePrimitiveExpression("All")));
                var declarationKey = new CodeAttributeDeclaration("Key");
                declarationKey.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(1)));
                declarationKey.Arguments.Add(new CodeAttributeArgument("Column", new CodePrimitiveExpression(children.ReferenceColumn)));
                var declarationOneToManyAttribute = new CodeAttributeDeclaration("OneToManyAttribute");
                declarationOneToManyAttribute.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(2)));
                declarationOneToManyAttribute.Arguments.Add(new CodeAttributeArgument("ClassType", new CodePrimitiveExpression("typeof(" + children.Reference + ")")));
                newType.Members.Add(codeGenerationHelper.CreateAutoProperty("IList", children.Reference, "", declarationBag, declarationKey, declarationOneToManyAttribute));
            }
            return compileUnit;
        }

        private void WriteToFile(CodeCompileUnit compileUnit, string className)
        {
            var provider = (CodeDomProvider)new CSharpCodeProvider();
            string sourceFile = GetCompleteFilePath(provider, className.MakeSingular());
            using (provider)
            {
                var streamWriter = new StreamWriter(sourceFile);
                var textWriter = new IndentedTextWriter(streamWriter, "    ");
                using (textWriter)
                {
                    using (streamWriter)
                    {
                        var options = new CodeGeneratorOptions { BlankLinesBetweenMembers = true };
                        provider.GenerateCodeFromCompileUnit(compileUnit, textWriter, options);
                    }
                }
            }
            CleanupGeneratedFile(sourceFile);
        }

        private static void CleanupGeneratedFile(string sourceFile)
        {
            string entireContent;
            using (var reader = new StreamReader(sourceFile))
            {
                entireContent = reader.ReadToEnd();
            }
            entireContent = RemoveComments(entireContent);
            entireContent = AddStandardHeader(entireContent);
            entireContent = FixAutoProperties(entireContent);
            using (var writer = new StreamWriter(sourceFile))
            {
                writer.Write(entireContent);
            }
        }

        // Hack : Auto property generator is not there in CodeDom.
        private static string FixAutoProperties(string entireContent)
        {
            entireContent = entireContent.Replace(@"{
        }", "{ }");
            entireContent = entireContent.Replace(
                @"{
            get {
            }
            set {
            }
        }", "{ get; set; }");
            return entireContent;
        }

        private static string AddStandardHeader(string entireContent)
        {
            entireContent = "using NHibernate.Mapping.Attributes;" + Environment.NewLine + entireContent;
            entireContent = "using System.Collections.Generic;" + Environment.NewLine + entireContent;
            entireContent = "using System.Collections;" + Environment.NewLine + entireContent;
            entireContent = "using System.Text;" + Environment.NewLine + entireContent;
            entireContent = "using System;" + Environment.NewLine + entireContent;
            return entireContent;
        }

        private static string RemoveComments(string entireContent)
        {
            int end = entireContent.LastIndexOf("----------");
            entireContent = entireContent.Remove(0, end + 10);
            return entireContent;
        }

        private string GetCompleteFilePath(CodeDomProvider provider, string className)
        {
            string fileName = filePath + className;
            return provider.FileExtension[0] == '.'
                       ? fileName + provider.FileExtension
                       : fileName + "." + provider.FileExtension;
        }
    }
}